﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AzureDiagMon.Common.Algorithms.FragmentedRanges
{
    /// <summary>
    /// Represents a range of some "loaded" items (sections).
    /// Allows different strategies to fill the holes between loaded sections.
    /// The sections StartIndex is inclusive, EndIndex is Exclusive.
    /// </summary>
    /// <typeparam name="TRangeIndex">Type of indexer object</typeparam>
    public class FragmentedRange<TRangeIndex> : FragmentedRange, IFragmentedRange
        where TRangeIndex : IComparable, new()
    {
        #region Public properties

        public TRangeIndex StartIndex { get; set; }
        public TRangeIndex EndIndex { get; set; }
        public FragmentedRange<TRangeIndex> CurrentViewSection { get; set; }

        #endregion

        #region Private fields

        private List<LoadedRangeSection<TRangeIndex>> loadedSections;
        private List<FragmentedRangeSection<TRangeIndex>> outOfRangeSections = new List<FragmentedRangeSection<TRangeIndex>>();

        #endregion

        #region Constructor

        public FragmentedRange(TRangeIndex startIndex, TRangeIndex endIndex,
                               IEnumerable<LoadedRangeSection<TRangeIndex>> loadedSections,
                               IFragmentedRangeResolverStrategy resolverStrategy)
            : base(resolverStrategy)
        {
            StartIndex = startIndex;
            EndIndex = endIndex;

            this.loadedSections = new List<LoadedRangeSection<TRangeIndex>>();

            if (loadedSections != null)
                AddLoadedSections(loadedSections);

        }

        /// <summary>
        /// Add loaded sections
        /// </summary>
        /// <param name="loadedSections"></param>
        public void AddLoadedSections(IEnumerable<LoadedRangeSection<TRangeIndex>> loadedSections)
        {
            var outOfRangeSections = loadedSections
                    .Where(f => f.EndIndex.CompareTo(StartIndex) <= 0
                         || f.StartIndex.CompareTo(EndIndex) >= 0);

            //Skip items which fully out of range
            this.loadedSections.AddRange(loadedSections.Except(outOfRangeSections));
            this.outOfRangeSections.AddRange(outOfRangeSections.Cast<FragmentedRangeSection<TRangeIndex>>());
        }

        public IEnumerable<IFragmentedRangeSection> GetConsolidatedSections()
        {
            return loadedSections.Cast<FragmentedRangeSection<TRangeIndex>>()
                                 .Consolidate()
                                 .Cast<IFragmentedRangeSection>();
        }

        public IEnumerable<IFragmentedRangeSection> GetAllSections()
        {
            return loadedSections.Cast<FragmentedRangeSection<TRangeIndex>>()
                                 .Consolidate()
                                 .Select(f => new LoadedRangeSection<TRangeIndex>(f))
                                 .Cast<IFragmentedRangeSection>()
                                 .Union(GetEmptySections().Cast<FragmentedRangeSection<TRangeIndex>>()
                                                          .Select(f => new EmptyRangeSection<TRangeIndex>(f))
                                                          .Cast<IFragmentedRangeSection>())
                                 .ToList();
        }

        public IEnumerable<IFragmentedRangeSection> GetOutOfRangeSections()
        {
            return outOfRangeSections.Cast<IFragmentedRangeSection>();
        }


        #endregion

        #region Overrides of FragmentedRange

        public override IEnumerable<IFragmentedRangeSection> GetEmptySections()
        {
            var result = new List<IFragmentedRangeSection>();
            if (loadedSections.Count == 0)
            {
                result.Add(new EmptyRangeSection<TRangeIndex>(
                    StartIndex, EndIndex));

                return result;
            }

            var consolidatedSections = GetConsolidatedSections().Cast<FragmentedRangeSection<TRangeIndex>>();

            var index = StartIndex;
            foreach (var section in consolidatedSections)
            {
                if (index.CompareTo(section.StartIndex) < 0)
                {
                    result.Add(new EmptyRangeSection<TRangeIndex>(
                        index, (TRangeIndex)section.StartIndex));
                }
                index = (TRangeIndex)section.EndIndex;
            }

            var lastSection = consolidatedSections.Last();
            if (lastSection.EndIndex.CompareTo(EndIndex) < 0)
            {
                result.Add(new EmptyRangeSection<TRangeIndex>(
                    (TRangeIndex)lastSection.EndIndex, EndIndex));
            }

            return result;
        }

        #endregion

        #region Public methods

        public IFragmentedRangeSection GetNextSection()
        {
            return ResolverStrategy.GetNextSection(this);
        }

        #endregion

        #region Overrides of Object

        public override string ToString()
        {
            return String.Format("FragmentedRange: {0} - {1} (Stategy: {2})", StartIndex, EndIndex, ResolverStrategy.GetType().Name);
        }

        #endregion

        #region Implementation of IFragmentedRangeSection

        IComparable IFragmentedRangeSection.StartIndex { get { return StartIndex; } }
        IComparable IFragmentedRangeSection.EndIndex { get { return EndIndex; } }

        #endregion

        #region Implementation of IFragmentedRange

        IFragmentedRangeSection IFragmentedRange.CurrentViewSection
        {
            get { return CurrentViewSection; }
        }

        IEnumerable<IFragmentedRangeSection> IFragmentedRange.LoadedSections
        {
            get { return loadedSections.Cast<IFragmentedRangeSection>(); }
        }

        #endregion

        #region Implementation of IComparable

        public int CompareTo(object obj)
        {
            var other = obj as FragmentedRange<TRangeIndex>;
            if (other == null)
                throw new NotSupportedException();

            return StartIndex.CompareTo(other.StartIndex);
        }

        #endregion
    }
}
